/*
 * Cpphtml is a utility to convert your C++ code to HTML. If you have 
 * a C++ file, say myprogram.cpp, and you want to put it on your website, 
 * you can run it through Cpphtml which will convert the code to HTML 
 * with all comments, keywords and preprocessor directives highlighted. 
 * Cpphtml will send all output to cout, so you have to redirect the 
 * output to a file if you want to create a HTML file:
 * http://www.codeproject.com/KB/cpp/cpphtml.aspx
 * http://home.scarlet.be/zoetrope/cpphtml.htm
 * 
 */
#include<stdio.h>
#include<iostream>
#include<fstream>
#include<string>
#include<ctype.h>

#define _TABSIZE	8

using namespace std;

int tabsize = _TABSIZE;

class token {
public:
  token() : _what(code) {}
protected:
  enum type {code, comment, pp, keyword, strings, command, number};
  string _str;
  type _what;
  friend istream& operator>>(istream&, token&);
  friend ostream& operator<<(ostream&, const token&);
};

/* Identifica comandos */
bool iscommand(const string& s) {
  static const char* commands[] = {
    "break",
    "case",
    "const",
    "continue",
    "default",
    "do",
    "else",
    "enum",
    "explicit",
    "export",
    "extern",
    "if",
    "for",
    "return",
    "struct",
    "switch",
    "typedef",
    "while"
  };

  for (int i = 0; i < sizeof(commands) / sizeof(char*); i++)
    if (string(commands[i]) == s)
      return true;

  return false;
}

/* Identifica keywords */
bool iskeyword(const string& s)
{
  static const char* keywords[] = {
    "and",
    "and_eq",
    "asm",
    "auto",
    "bitand",
    "bitor",
    "bool",
    "catch",
    "char",
    "class",
    "compl",
    "const_cast",
    "delete",
    "double",
    "dynamic_cast",
    "false",
    "float",
    "friend",
    "goto",
    "inline",
    "int",
    "long",
    "mutable",
    "namespace",
    "new",
    "not",
    "not_eq",
    "operator",
    "or",
    "or_eq",
    "private",
    "protected",
    "public",
    "register",
    "reinterpret_cast",
    "short",
    "signed",
    "sizeof",
    "static",
    "static_cast",
    "template",
    "this",
    "throw",
    "true",
    "try",
    "typeid",
    "typename",
    "union",
    "unsigned",
    "using",
    "virtual",
    "void",
    "volatile",
    "wchar_t",
    "xor",
    "xor_eq"
  };

  for (int i = 0; i < sizeof(keywords) / sizeof(char*); i++)
    if (string(keywords[i]) == s)
      return true;

  return false;
}

bool containspp(const string& s)
{
  static const char* pptokens[] = {
    "define",
    "elif",
    "else",
    "endif",
    "error",
    "if",
    "ifdef",
    "ifndef",
    "include",
    "line",
    "pragma",
    "undef"
  };

  for (int i = 0; i < sizeof(pptokens) / sizeof(char*); i++)
    if (s.find(pptokens[i]) != string::npos)
      return true;

  return false;
}

istream& operator>>(istream& is, token& t)
{
  t._str = "", t._what = token::code;
  int c = is.get();
  switch (c) {
  case '/':
    c = is.get();
    if (c == '*') {
      t._str = "/*";
      t._what = token::comment;
      while (1) {
	c = is.get();
	if (c == EOF)
	  return is.unget(), is.clear(), is;
	if (c == '/') {
	  if (t._str.length() > 2 &&
	      t._str[t._str.length() - 1] == '*') {
	    return t._str += '/', is;
	  }
	}
	t._str += (char)c;
      }
    } else if (c == '/') {
      t._str = "//";
      t._what = token::comment;
      c = is.get();
      while (c != '\n' && c != EOF) {
	t._str += (char)c;
	c = is.get();
      }
      if (c == '\n') {
	t._str += '\n';
      }
      return is;
    }
    t._str = '/';
    return is.unget(), is.clear(), is;
	  
  case '#':
    t._str = '#';
    c = is.get();
    while (strchr(" \r\n\t", c)) {
      t._str += (char)c;
      c = is.get();
    }
    if (c == EOF)
      return is.unget(), is.clear(), is;

    while (strchr("abcdefghijklmnopqrstuvwxyz \t <>.", c)) {
      t._str += (char)c;
      c = is.get();
    }
    is.unget(), is.clear();
    if (containspp(t._str))
      t._what = token::pp;
    return is;
  case '\'':
  case '"': {
    char q = (char)c;
    t._str = q;
    t._what = token::strings;
    while (1) {
      c = is.get();
      if (c == EOF)
	return is.unget(), is.clear(), is;
      if (c == q) {
	if (t._str.length() >= 2) {
	  if (!(t._str[t._str.length() - 1] == '\\' &&
		t._str[t._str.length() - 2] != '\\'))
	    return t._str += q, is;
	} else {
	  return t._str += q, is;
	}
      }
      t._str += (char)c;				
    }
  }
  case 'a':
  case 'b':
  case 'c':
  case 'd':
  case 'e':
  case 'f':
  case 'g':
  case 'i':
  case 'l':
  case 'm':
  case 'n':
  case 'o':
  case 'p':
  case 'r':
  case 's':
  case 't':
  case 'u':
  case 'v':
  case 'w':
  case 'x':
    t._str += (char)c;
    c = is.get();
    while (isalpha(c) || isdigit(c) || c == '_') {
      t._str += (char)c;
      c = is.get();
    }
    is.unget(), is.clear();
    if (iscommand(t._str)) 
      t._what = token::command;
    else if (iskeyword(t._str))
      t._what = token::keyword;
    return is;
  case EOF:
    return is;
  default:
    t._str += (char)c;
    c = is.get();
    while (c != '/' && c != '#' && !strchr("abcdefgilmnoprstuvwx", c) &&
	   c != '\'' && c != '"' && c != EOF) {
      t._str += (char)c;
      c = is.get();
    }
    is.unget(), is.clear();
    return is;
  }
}

string html(const string& s)
{
  string s1;
  string::size_type i;
  for (i = 0; i < s.length(); i++) {
    switch (s[i]) {
    case '&':
      s1 += "&amp;";
      break;
    case '<':
      s1 += "&lt;";
      break;
    case '>':
      s1 += "&gt;";
      break;
    case '"':
      s1 += "&quot;";
      break;
    case '\t':
      s1.append(tabsize, ' ');
      break;
    default:
      s1 += s[i];
    }
  }
  return s1;
}

ostream& operator<<(ostream& os, const token& t)
{
  if (t._what == token::code)
    cout << html(t._str);
  else if (t._what == token::comment)
    cout << "<span class=comment>" << html(t._str) << "</span>";
  else if (t._what == token::keyword)
    cout << "<span class=keyword>" << html(t._str) << "</span>";
  else if (t._what == token::pp)
    cout << "<span class=pp>" << html(t._str) << "</span>";
  else if (t._what == token::strings)
    cout << "<span class=strings>" << html(t._str) << "</span>";
  else if (t._what == token::command)
    cout << "<span class=command>" << html(t._str) << "</span>";
  else if (t._what == token::number)
    cout << "<span class=number>" << html(t._str) << "</span>";
  else
    cout << html(t._str);
  return os;
}

int main(int argc, char **argv)
{
  if (argc != 2 && argc != 3) {
    cout << "usage: cpphtml file [tab size]" << endl;
    return 0;
  }
  ifstream is(argv[1]);
  if (!is.good()) {
    cerr << "bad input file" << endl;
    return -1;
  }
  if (argc == 3) {
    tabsize = atoi(argv[2]);
    if (tabsize <= 0)
      tabsize = _TABSIZE;
  }

  cout << "<pre style=\"font-family:\"Courier New\",Courier,Monospace;font-size:small\">";
  token t;
  while (is >> t) {
    cout << t;
  }
  cout << "</pre>" << endl;

  return 0;
}
